/**
 * Copyright (c) 2013 Mark Tielemans
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * DISCLAIMER
 * This software is provided "as is" and any expressed or implied warranties, including, but not limited to, the implied warranties of 
 * merchantability and fitness for a particular purpose are disclaimed. In no event shall the regents or contributors be liable for any 
 * direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute 
 * goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in 
 * contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if 
 * advised of the possibility of such damage.
 */
package nl.marktielemans.easydb.model;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import nl.marktielemans.easydb.EasyDBConfig;
import nl.marktielemans.easydb.connection.DatabaseConnector;
import nl.marktielemans.easydb.generator.QueryGenerator;

import org.apache.log4j.Logger;

/**
 * Represents a table in the database. CRUD methods are implemented through the use of the chosen 
 * {@link QueryGenerator}. For this to work, the {@link #getColumns()} method must be properly 
 * implemented.
 * 
 * @author Mark Tielemans
 * @since 1.0
 * @version 1.0
 * @see EasyDBConfig#setQueryGenerator(QueryGenerator)
 */
public abstract class DatabaseTable {

	public static final Logger logger = Logger.getLogger(DatabaseTable.class);
	
	protected List<TableColumn<?>> tableColumns = new ArrayList<TableColumn<?>>();
	String TABLE_NAME;

	private DatabaseTable() {}
	
	/**
	 * On creation of a new {@link DatabaseTable}, the name property is required.
	 * @param tableName as in the database. This is used directly in SQL queries generated by the 
	 * {@link QueryGenerator}.
	 */
	public DatabaseTable(String tableName) {
		this();
		TABLE_NAME = tableName;
	}
	
	/**
	 * Ensure that this table exists in the database by executing a <code>CREATE IF NOT EXISTS</code> 
	 * statement.
	 * @throws SQLException
	 * @see QueryGenerator#generateCreateTableQuery(DatabaseTable)
	 */
	public void createTable() throws SQLException {
		logger.info("Executing create table statement for table '"+TABLE_NAME+"'");
		String sqlStatement = EasyDBConfig.get().getQueryGenerator().generateCreateTableQuery(this);
		DatabaseConnector db = EasyDBConfig.get().getConnector();
		db.update(sqlStatement);
	}
	
	/**
	 * Drop this table from the database.
	 * @throws SQLException
	 * @see QueryGenerator#generateDropQuery(DatabaseTable)
	 */
	public void dropTable() throws SQLException {
		logger.info("Dropping table '"+TABLE_NAME+"'");
		String sqlStatement = EasyDBConfig.get().getQueryGenerator().generateDropQuery(this);
		DatabaseConnector db = EasyDBConfig.get().getConnector();
		db.update(sqlStatement);
	}
	
	/**
	 * Generate an insert query for this table using properties from the given {@link EasyDBEntity}.
	 * 
	 * @param entity
	 * @throws SQLException
	 * @see QueryGenerator#generateInsertQuery(DatabaseTable, EasyDBEntity)
	 * @see EasyDBEntity
	 */
	public void insertNew(EasyDBEntity entity) throws SQLException {
		logger.info("Insert on table '"+TABLE_NAME+"'");
		String sqlStatement = EasyDBConfig.get().getQueryGenerator().generateInsertQuery(this, entity);
		DatabaseConnector db = EasyDBConfig.get().getConnector();
		db.update(sqlStatement);
	}
	
	/**
	 * Generate an update query for this table using properties from the given {@link EasyDBEntity} 
	 * and the given list of propertyvalues as conditions.
	 * 
	 * @param entity
	 * @param conditions
	 * @throws SQLException
	 * @see QueryGenerator#generateUpdateQuery(DatabaseTable, EasyDBEntity, List)
	 * @see EasyDBEntity
	 */
	public void update(EasyDBEntity entity, List<TableColumn<?>> conditions) throws SQLException {
		logger.debug("Update on table '"+TABLE_NAME+"'");
		String sqlStatement = EasyDBConfig.get().getQueryGenerator().generateUpdateQuery(this, entity, conditions);
		DatabaseConnector db = EasyDBConfig.get().getConnector();
		db.update(sqlStatement);
	}
	
	/**
	 * Generate a delete query for entries of this table by the given list of condition property values.
	 * @param conditions
	 * @throws SQLException
	 * @see QueryGenerator#generateDeleteQuery(DatabaseTable, List)
	 */
	public void delete(List<TableColumn<?>> conditions) throws SQLException {
		logger.debug("Delete on table '"+TABLE_NAME+"'");
		String sqlStatement = EasyDBConfig.get().getQueryGenerator().generateDeleteQuery(this, conditions);
		DatabaseConnector db = EasyDBConfig.get().getConnector();
		db.update(sqlStatement);
	}
	
	/**
	 * Query this table for entries by the given list of property value conditions.
	 * @param conditions
	 * @return set of results
	 * @throws SQLException
	 * @see QueryGenerator#generateSimpleSelectQuery(DatabaseTable, List)
	 */
	public ResultSet simpleSelect(List<TableColumn<?>> conditions) throws SQLException {
		logger.debug("Select from table '"+TABLE_NAME+"'");
		String sqlStatement = EasyDBConfig.get().getQueryGenerator().generateSimpleSelectQuery(this, conditions);
		DatabaseConnector db = EasyDBConfig.get().getConnector();
		return db.query(sqlStatement);
	}
	
	/**
	 * Classes overriding {@link DatabaseTable} must include their own implementation of this method. This method should return all 
	 * of the table's columns as in the database, in order to be able to use the standard CRUD methods.
	 * @return list of table columns
	 * @see QueryGenerator
	 */
	public abstract List<TableColumn<?>> getColumns();
	
	/**
	 * Retrieve a human readable String representation of this table, meant for debugging. The String 
	 * lists the table name and its columns.
	 *  
	 * @return String representation of this table
	 */
	public String toString() {
		String s = "Database table '"+TABLE_NAME+"' with columns: ";
		for(TableColumn<?> col : getColumns()) {
			s+="\n\t"+col.getPropertyName()+" ("+col.getType().getName()+", "+col.getLength()+")";
		}
		return s;
	}
	
	/**
	 * Retrieve this table's name as in the database.
	 * @return table name
	 */
	public String getName() {
		return TABLE_NAME;
	}
}
